{
 "cells": [
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "import os\n",
    "from langchain_openai import ChatOpenAI\n",
    "\n",
    "os.environ[\"OPENAI_API_KEY\"] = os.getenv(\"CHAT_GPT_APIKEY\")\n",
    "os.environ[\"OPENAI_API_BASE\"] = os.getenv(\"CHAT_GPT_APIURL\")\n",
    "os.environ[\"SERPAPI_API_KEY\"] = os.getenv(\"SERPAPI_API_KEY\")\n",
    "\n",
    "llm = ChatOpenAI()"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "import requests\n",
    "import json\n",
    "from langchain.agents import tool\n",
    "\n",
    "@tool\n",
    "def searxng_search(query):\n",
    "    \"\"\"\n",
    "        使用searchng搜索\n",
    "    \"\"\"\n",
    "    SEARXNG_URL = \"http://localhost:6688/search\"\n",
    "    params = {}\n",
    "    params['q'] = query\n",
    "    params['format'] = 'json'\n",
    "    params['engines'] = 'bing'\n",
    "\n",
    "    response = requests.get(SEARXNG_URL, params=params)\n",
    "    if response.status_code == 200:\n",
    "        res = response.json()\n",
    "        resList = []\n",
    "        for item in res['results']:\n",
    "            resList.append({\n",
    "                \"title\": item['title'],\n",
    "                \"content\": item['content'],\n",
    "                \"url\": item['url']\n",
    "            })\n",
    "            if len(resList) >= 3:\n",
    "                break\n",
    "        return resList\n",
    "    else:\n",
    "        response.raise_for_status()\n",
    "\n",
    "searxng_search.invoke(\"李连杰\")"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "promptTemplate = \"\"\"尽可能的帮助用户回答任何问题。\n",
    "\n",
    "您可以使用以下工具来帮忙解决问题，如果已经知道了答案，也可以直接回答：\n",
    "\n",
    "searxng_search : searxng_search(query) -> 输入搜索内容，使用 SearXNG 进行搜索。\n",
    "\n",
    "回复格式说明\n",
    "----------------------------\n",
    "\n",
    "回复我时，请以以下两种格式之一输出回复：\n",
    "\n",
    "选项 1：如果您希望人类使用工具，请使用此选项。\n",
    "采用以下JSON模式格式化的回复内容,回复的格式里不要有注释内容：\n",
    "\n",
    "```json\n",
    "{{\n",
    "    \"reason\": string, \\\\ 叙述使用工具的原因\n",
    "    \"action\": \"searxng_search\", \\\\ 要使用的工具。 必须是 searxng_search\n",
    "    \"action_input\": string \\\\ 工具的输入\n",
    "}}\n",
    "````\n",
    "\n",
    "选项2：如果您认为你已经有答案或者已经通过使用工具找到了答案，想直接对人类做出反应，请使用此选项。 采用以下JSON模式格式化的回复内容,回复的格式里不要有注释内容：\n",
    "\n",
    "```json\n",
    "{{\n",
    "  \"action\": \"Final Answer\",\n",
    "  \"answer\": string \\\\最终答复问题的答案放到这里！\n",
    "}}\n",
    "````\n",
    "\n",
    "用户的输入\n",
    "--------------------\n",
    "这是用户的输入（请记住通过单个选项，以JSON模式格式化的回复内容,回复的格式里不要有注释内容，不要回复其他内容）：\n",
    "\n",
    "{input}\n",
    "\n",
    "\"\"\""
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "from langchain_core.prompts import ChatPromptTemplate, MessagesPlaceholder\n",
    "\n",
    "prompt = ChatPromptTemplate.from_messages(\n",
    "    [\n",
    "        (\n",
    "            \"system\",\n",
    "            \"你是非常强大的助手，你可以使用各种工具来完成人类交给你的问题和任务。\"\n",
    "        ),\n",
    "        (\n",
    "            \"user\",\n",
    "            promptTemplate\n",
    "        ),\n",
    "    ]\n",
    ")"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "from langchain.agents.agent import AgentOutputParser\n",
    "from langchain_core.output_parsers.json import parse_json_markdown\n",
    "from langchain_core.exceptions import OutputParserException\n",
    "from langchain_core.agents import AgentAction, AgentFinish\n",
    "from langchain_core.output_parsers import StrOutputParser\n",
    "\n",
    "class JSONAgentOutputParser(AgentOutputParser):\n",
    "    def parse(self, text):\n",
    "        try:\n",
    "            response = parse_json_markdown(text)\n",
    "            if isinstance(response, list):\n",
    "                # 经常忽略发出单个动作的指令\n",
    "                print(\"Got multiple action responses: %s\", response)\n",
    "                response = response[0]\n",
    "            if response[\"action\"] == \"Final Answer\":\n",
    "                return AgentFinish({\"output\": response[\"answer\"]}, text)\n",
    "            else:\n",
    "                return AgentAction(\n",
    "                    response[\"action\"], response.get(\"action_input\", {}), text\n",
    "                )\n",
    "        except Exception as e:\n",
    "            raise OutputParserException(f\"Could not parse LLM output: {text}\") from e\n",
    "\n",
    "    @property\n",
    "    def _type(self) -> str:\n",
    "        return \"json-agent\""
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "output_parser = StrOutputParser()\n",
    "chain1 = prompt | llm \n",
    "\n",
    "m = chain1.invoke({\"input\":\"小米su7的发布时间\"})\n",
    "print(m)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "m = chain1.invoke({\"input\":\"请使用中文给我讲个笑话吧?\"})\n",
    "m"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "promptTemplate = \"\"\"使用浏览器获取的搜索内容：\n",
    "---------------------\n",
    "{observation}\n",
    "---------------------\n",
    "请根据浏览器的响应，回答下面的问题：\n",
    "\n",
    "{input}\"\"\"\n",
    "\n",
    "prompt2 = ChatPromptTemplate.from_messages(\n",
    "    [\n",
    "        (\n",
    "            \"system\",\n",
    "            \"你是非常强大的助手，你可以使用各种工具来完成人类交给的问题和任务。\",\n",
    "        ),\n",
    "        (\"user\", promptTemplate),\n",
    "    ]\n",
    ")\n"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "chain2 = prompt2 | llm "
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "result = searxng_search.invoke(\"小米su7 发布时间\")\n",
    "result"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "chain2.invoke({\"input\":\"小米su7的发布时间\",\"observation\":result})"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "from langgraph.graph import END, MessageGraph\n",
    "\n",
    "graph = MessageGraph()\n",
    "\n",
    "graph.add_node(\"chain\", chain1)\n",
    "graph.add_edge(\"chain\", END)\n",
    "\n",
    "graph.set_entry_point(\"chain\")\n",
    "\n",
    "runnable1 = graph.compile()"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "from IPython.display import Image\n",
    "\n",
    "Image(runnable1.get_graph().draw_png())"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "def process(state):\n",
    "    print(state)\n",
    "    content = state[-1].content\n",
    "    return {\"input\":state[0].content,\"observation\":content}\n",
    "\n",
    "chain2 =  process | prompt2 | llm "
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "from langgraph.graph import END, MessageGraph\n",
    "from langchain_core.messages import HumanMessage\n",
    "\n",
    "graph = MessageGraph()\n",
    "\n",
    "def tool(state):\n",
    "    print(state)\n",
    "    content = state[-1].content\n",
    "    response = parse_json_markdown(content)\n",
    "\n",
    "    result = searxng_search.invoke(response[\"action_input\"])\n",
    "    return HumanMessage(result)\n",
    "\n",
    "graph.add_node(\"chain1\", chain1)\n",
    "graph.add_node(\"tool\",tool)\n",
    "graph.add_node(\"chain2\", chain2)\n",
    "\n",
    "# 设置开始\n",
    "graph.set_entry_point(\"chain1\")\n",
    "\n",
    "# 设置条件边\n",
    "def router(state):\n",
    "    print(state)\n",
    "    content = state[-1].content\n",
    "    response = parse_json_markdown(content)\n",
    "    if response[\"action\"] == \"Final Answer\":\n",
    "        return \"end\"\n",
    "    else:\n",
    "        return \"tool\"\n",
    "\n",
    "graph.add_conditional_edges(\"chain1\", router, {\n",
    "    \"tool\": \"tool\",\n",
    "    \"end\": END,\n",
    "})\n",
    "\n",
    "graph.add_edge(\"tool\", \"chain2\")\n",
    "graph.add_edge(\"chain2\", END)\n",
    "\n",
    "\n",
    "runnable2 = graph.compile()"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "from IPython.display import Image\n",
    "\n",
    "Image(runnable2.get_graph().draw_png())"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "runnable2.invoke(HumanMessage(\"小米su7的发布时间\"))"
   ]
  }
 ],
 "metadata": {
  "kernelspec": {
   "display_name": "ai",
   "language": "python",
   "name": "python3"
  },
  "language_info": {
   "codemirror_mode": {
    "name": "ipython",
    "version": 3
   },
   "file_extension": ".py",
   "mimetype": "text/x-python",
   "name": "python",
   "nbconvert_exporter": "python",
   "pygments_lexer": "ipython3",
   "version": "3.10.12"
  }
 },
 "nbformat": 4,
 "nbformat_minor": 2
}
